#include "json_easy_parser.h"
#include <library/json/json_reader.h>
#include <util/string/split.h>
#include <util/string/strip.h>

namespace NJson {

static TString MAP_IDENTIFIER = "{}";
static TString ARRAY_IDENTIFIER = "[]";
static TString ANY_IDENTIFIER = "*";

static void ParsePath(TString path, yvector<TPathElem> *res) {
    yvector<const char*> parts;
    Split(path.begin(), '/', &parts);
    for (size_t n = 0; n < parts.size(); ++n) {
        TString part = strip(parts[n]);
        if (!part.empty()) {
            if (part[0] != '[') {
                res->push_back(TPathElem(NImpl::MAP));
                res->push_back(TPathElem(part));
            } else {
                int arrayCounter;
                try {
                    arrayCounter = FromString<int>(part.substr(1, part.length() - 2));
                } catch (yexception&) {
                    arrayCounter = -1;
                }
                res->push_back(TPathElem(arrayCounter));
            }
        }
    }
}

void TJsonParser::AddField(const TString &path, bool nonEmpty) {
    Fields.emplace_back();
    Fields.back().NonEmpty = nonEmpty;
    ParsePath(path, &Fields.back().Path);
}

TString TJsonParser::ConvertToTabDelimited(const TString &json) const {
    TStringInput in(json);
    TStringStream out;
    ConvertToTabDelimited(in, out);
    return out.Str();
}

class TRewriteJsonImpl: public NJson::TJsonCallbacks {
    const TJsonParser &Parent;
    yvector<TString> FieldValues;
    yvector<TPathElem> Stack;
    bool ShouldUpdateOnArrayChange;
    int CurrentFieldIdx;
    bool HasFormatError;

private:
    static bool PathElementMatch(const TPathElem &templ, const TPathElem &real) {
        if (templ.Type != real.Type)
            return false;
        if (templ.Type == NImpl::ARRAY)
            return templ.ArrayCounter == -1 || templ.ArrayCounter == real.ArrayCounter;
        if (templ.Type == NImpl::MAP_KEY)
            return templ.Key == ANY_IDENTIFIER || templ.Key == real.Key;
        return true;
    }

    bool CheckFilter(const yvector<TPathElem> &path) const {
        if (Stack.size() < path.size())
            return false;
        for (size_t n = 0; n < +path; ++n) {
            if (!PathElementMatch(path[n], Stack[n]))
                return false;
        }
        return true;
    }

    void UpdateRule() {
        for (size_t n = 0; n < +Parent.Fields; ++n) {
            if (FieldValues[n].empty() && CheckFilter(Parent.Fields[n].Path)) {
                CurrentFieldIdx = n;
                return;
            }
        }
        CurrentFieldIdx = -1;
    }

    void Pop() {
        Stack.pop_back();
    }

    void IncreaseArrayCounter() {
        if (!Stack.empty() && Stack.back().Type == NImpl::ARRAY) {
            ++Stack.back().ArrayCounter;
            if (ShouldUpdateOnArrayChange)
                UpdateRule();
        }
    }

    template <class T> bool OnValue(const T& val) {
        IncreaseArrayCounter();
        if (CurrentFieldIdx >= 0) {
            FieldValues[CurrentFieldIdx] = ToString(val);
            UpdateRule();
        }
        return true;
    }

public:
    TRewriteJsonImpl(const TJsonParser &parent)
        : Parent(parent)
        , FieldValues(parent.Fields.size())
        , ShouldUpdateOnArrayChange(false)
        , CurrentFieldIdx(-1)
        , HasFormatError(false)
    {
        for (size_t n = 0; n < +Parent.Fields; ++n) {
            if (Parent.Fields[n].Path.back().Type == NImpl::ARRAY)
                ShouldUpdateOnArrayChange = true;
        }
    }

    bool OnOpenMap() override {
        IncreaseArrayCounter();
        Stack.push_back(TPathElem(NImpl::MAP));
        if (CurrentFieldIdx >= 0)
            HasFormatError = true;
        else
            UpdateRule();
        return true;
    }

    bool OnOpenArray() override {
        IncreaseArrayCounter();
        Stack.push_back(TPathElem(-1));
        if (CurrentFieldIdx >= 0)
            HasFormatError = true;
        else
            UpdateRule();
        return true;
    }

    bool OnCloseMap() override {
        while (!Stack.empty() && Stack.back().Type != NImpl::MAP)
            Pop();
        if (!Stack.empty())
            Pop();
        UpdateRule();
        return true;
    }

    bool OnCloseArray() override {
        if (!Stack.empty())
            Pop();
        UpdateRule();
        return true;
    }

    bool OnMapKey(const TStringBuf &key) override {
        if (!Stack.empty() && Stack.back().Type == NImpl::MAP_KEY) {
            Pop();
            UpdateRule();
        }
        Stack.push_back(TPathElem(key.ToString()));
        if (CurrentFieldIdx >= 0)
            HasFormatError = true;
        else
            UpdateRule();
        return true;
    }

    bool OnBoolean(bool b) override {
        return OnValue(b);
    }

    bool OnInteger(long long i) override {
        return OnValue(i);
    }

    bool OnDouble(double f) override {
        return OnValue(f);
    }

    bool OnString(const TStringBuf &str) override {
        return OnValue(str);
    }

    bool IsOK() const {
        if (HasFormatError)
            return false;
        for (size_t n = 0; n < +FieldValues; ++n)
            if (Parent.Fields[n].NonEmpty && FieldValues[n].empty())
                return false;
        return true;
    }

    void WriteTo(TOutputStream &out) const {
        for (size_t n = 0; n < +FieldValues; ++n)
            out << "\t" << FieldValues[n];
    }

    void WriteTo(yvector<TString> *res) const {
        *res = FieldValues;
    }
};

void TJsonParser::ConvertToTabDelimited(TInputStream &in, TOutputStream &out) const {
    TRewriteJsonImpl impl(*this);
    ReadJson(&in, &impl);
    if (impl.IsOK()) {
        out << Prefix;
        impl.WriteTo(out);
        out.Flush();
    }
}

bool TJsonParser::Parse(const TString &json, yvector<TString> *res) const {
    TRewriteJsonImpl impl(*this);
    TStringInput in(json);
    ReadJson(&in, &impl);
    if (impl.IsOK()) {
        impl.WriteTo(res);
        return true;
    } else
        return false;
}

//struct TTestMe {
//    TTestMe() {
//        TJsonParser worker;
//        worker.AddField("/x/y/z", true);
//        TString ret1 = worker.ConvertToTabDelimited("{ \"x\" : { \"y\" : { \"w\" : 1, \"z\" : 2 } } }");
//        TString ret2 = worker.ConvertToTabDelimited(" [1, 2, 3, 4, 5] ");
//    }
//} testMe;

}
